package  
{
	import data.*;
	import com.adobe.serialization.json.*;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.text.Font;
	import flash.text.TextFormat;
	import flash.events.*;
    import flash.net.*;
	
	/**
	 * Various utility functions go in here
	 * @author Roy, Owen
	 */
	public class GCUtil 
	{
		public static var _format:TextFormat;
		
		public static function getTextFormat():TextFormat
		{
			if (_format == null)
			{
				_format = new TextFormat();
				_format.font = 'Arial';
				_format.size = 18;
			}
			
			return _format;
		}
		
		/**
		 * Get a random computer name
		 * 
		 * @return
		 */
		public static function getRandomComputerName():String
		{
			var randomIndex:uint = Math.floor(Math.random() * GCConstants.COMPUTER_NAMES.length);
			return GCConstants.COMPUTER_NAMES[randomIndex];
		}
		
		/**
		 * Get a random computer level
		 * 
		 * @return
		 */
		public static function getRandomComputerLevel():uint
		{
			return Math.floor(Math.random() * 60) + 1;
		}
		
		// Keep track of whether to play a sound effect
		public static var _playSoundEffects:Boolean = true;
		
		/*
		 * Network stubs. These set up JSON payloads and call send request.
		 * They require callbacks. Callbacks receive two arguments uint and Array
		 * Objects. uint result is error code as defined by backend (e.g. 0 for 
		 * success. Array is a list of row Objects from database backend.
		 */		
		
		 /**
		  * Request to add a new player into the game
		  * 
		  * @param	email
		  * 		A String id for the new player
		  * @param 	lid
		  * 		The location in which to drop the new player
		  * @return
		  */
		public static function addPlayer(email:String, lid:uint, playerName:String, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.ADD_PLAYER;
			payload["email"] = email;
			payload["lid"] = lid;
			payload["name"] = playerName;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function deleteShip(shipId:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.DELETE_SHIP;
			payload["shipId"] = shipId;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function updateGold(pid:uint, amt:int, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.UPDATE_GOLD;
			payload["pid"] = pid;
			payload["amt"] = amt;
			
			sendRequest(payload, callback);
			return true;
		}
		
		// links to dropItem in DBHandler
		public static function removeFromInventory(itemId:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.REMOVE_FROM_INVENTORY;
			payload["invItemId"] = itemId;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function getBuildings(pid:uint, lid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_BUILDINGS;
			payload["pid"] = pid;
			payload["lid"] = lid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function getPlayerItemsForSale(pid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_PLAYER_ITEMS_FOR_SALE;
			payload["pid"] = pid;
			
			sendRequest(payload, callback);
			return true;			
		}
		
		public static function getPlayerItemsForSaleAt(pid:uint, lid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_PLAYER_ITEMS_FOR_SALE_AT;
			payload["pid"] = pid;
			payload["lid"] = lid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function getCrewAt(pid:uint, lid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_CREW_AT;
			payload["pid"] = pid;
			payload["lid"] = lid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function changePlayerLocation(pid:uint, lid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.CHANGE_PLAYER_LOCATION;
			payload["pid"] = pid;
			payload["lid"] = lid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function updateQuestMinLeft(id:uint, newMinLeft:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.UPDATE_QUEST_MIN_LEFT;
			payload["id"] = id;
			payload["newMinLeft"] = newMinLeft;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to create a brand new ship that the player can later use
		 * if they want to.
		 * 
		 * @param	pid
		 * @param	shipTypeId
		 * @param	capacity
		 * @param	defense
		 * @param	consRes1
		 * @param	consQty1
		 * @param	consRes2
		 * @param	consQty2
		 * @return
		 */

		public static function addShip(pid:uint, shipTypeId:uint, 
		capacity:uint, defense:uint, speed:uint, navigation:uint,
		slots:uint, maintenance:uint, fuelEfficiency:uint,
		consRes1:uint, consQty1:uint, consRes2:uint, 
		consQty2:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.ADD_SHIP;
			payload["pid"] = pid;
			payload["shipTypeId"] = shipTypeId;
			payload["speed"] = speed;
			payload["defense"] = defense;
			payload["navigation"] = navigation;
			payload["slots"] = slots;
			payload["capacity"] = capacity;
			payload["maintenance"] = maintenance;
			payload["fuelEfficiency"] = fuelEfficiency;
			payload["consRes1"] = consRes1;
			payload["consQty1"] = consQty1;
			payload["consRes2"] = consRes2;
			payload["consQty2"] = consQty2;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to add new crew to the specified location and to perform the specified
		 * task
		 * 
		 * @param	pid
		 * 			The unique player id
		 * @param	lidd
		 * 			The location id of where to put the new worker. (defaults to ship)
		 * @param	tid
		 * 			The task to give the new worker. (defaults to idle)
		 * @return
		 */
		public static function addCrew(pid:uint, lid:uint, tid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.ADD_CREW;
			payload["pid"] = pid;
			payload["lid"] = lid;
			payload["tid"] = tid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function updateCrew(pid:uint, oldlid:uint, oldtid:uint,
				newlid:uint, newtid:uint, qty:uint, callback:Function):Boolean {
			var payload:Object = new Object();
			payload["action"] = GCConstants.UPDATE_CREW;
			payload["pid"] = pid;
			payload["oldlid"] = oldlid;
			payload["oldtid"] = oldtid;
			payload["newlid"] = newlid;
			payload["newtid"] = newtid;
			payload["qty"] = qty;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to add an item to the inventory
		 * 
		 * @param itemId
		 * 		The id of the item to be added
		 * @param lid
		 * 		The id of the location to add the item (can be ship or city)
		 * @param
		 * 		The quantity of the item to be added
		 * @return
		 */
		public static function addToInventory(pid:uint, itemId:uint, lid:uint, quantity:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.ADD_TO_INVENTORY;
			payload["pid"] = pid;
			payload["itemId"] = itemId;
			payload["lid"] = lid;
			payload["quantity"] = quantity;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to start a new quest
		 * 
		 * @param	pid
		 * 			The unique player id
		 * @param	minLeft
		 * 			The time limit of this quest.
		 * @param	templateId
		 * 			The template from which the quest description is derived from
		 * @param	fromId
		 * 			The name of the character asking for quest
		 * @param	lid
		 * 			The location where the quest must be completed
		 * @param	reqItemId
		 * 			The requested item
		 * @param	reqItemQty
		 * 			The requested
		 * @param	rewItemId
		 * 			The type of reward (can be item or gold)
		 * @param	rewItemQty
		 * 			The quantity of reward to give.
		 * @return
		 */
		public static function assignNewQuest(pid:uint, minLeft:uint, templateId:uint, fromId:uint, lid:uint, reqItemId:uint, reqItemQty:uint, rewItemId:uint, rewItemQty:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.ASSIGN_NEW_QUEST;
			payload["pid"] = pid;
			payload["minLeft"] = minLeft;
			payload["templateId"] = templateId;
			payload["fromId"] = fromId;
			payload["lid"] = lid;
			payload["reqItemId"] = reqItemId;
			payload["reqItemQty"] = reqItemQty;
			payload["rewItemId"] = rewItemId;
			payload["rewItemQty"] = rewItemQty;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to build a brand new structure in the given location
		 * 
		 * @param	pid
		 * 			The id of the player
		 * @param	buildingId
		 * 			The id of the building type
		 * @param	locationId
		 * 			The id of the city the building is to be constructed.
		 * @return
		 */
		public static function buildStructure(pid:uint, locationId:uint, buildingId:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.BUILD_STRUCTURE;
			payload["pid"] = pid;
			payload["locationId"] = locationId;
			payload["buildingId"] = buildingId;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function deleteBuilding(bldgId:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.DELETE_BUILDING;
			payload["bldgId"] = bldgId;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to buy an item
		 * 
		 * @param	pid
		 * 			The player's unique id
		 * @param	invItemId
		 * 			Unique id of the batch of items to purchase.
		 * @param	itemId
		 * 			The id of the type of item
		 * @param	salePrice
		 * 			Price to pay
		 * @param	qty
		 * 			Amount to buy
		 * @param	sellToLocId
		 * 			Location that purchase was made
		 * @return
		 */
		public static function buyItem(buyerId:uint, invItemId:uint, itemId:uint, salePrice:uint, qty:uint, sellToLocId:uint, sellerId:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.BUY_ITEM;
			payload["buyerPid"] = buyerId;
			payload["invItemId"] = invItemId;
			payload["itemId"] = itemId;
			payload["salePrice"] = salePrice;
			payload["qty"] = qty;
			payload["sellToLocId"] = sellToLocId;
			payload["sellerPid"] = sellerId;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to change the active ship the player uses in the game
		 * and can upgrade.
		 * 
		 * @param	pid
		 * 			The id of the player
		 * @param 	sid
		 * 			The id of the ship to change to (unique amongst all players)
		 * 
		 * @return
		 */
		public static function changeActiveShip(pid:uint, sid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.CHANGE_ACTIVE_SHIP;
			payload["pid"] = pid;
			payload["sid"] = sid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to end to the quest. Occurs if the player is successful or
		 * if the time limit just expires.
		 * 
		 * Use completed argument as uint (1 for completed, 0 for not).
		 * I'm not sure how object maps will treat booleans in AS.
		 */
		public static function completeQuest(qid:uint, completed:uint, pid:uint,
			reqItemId:uint, reqItemQty:uint, rewlid:uint, rewItemId:uint, rewQty:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.COMPLETE_QUEST;
			payload["qid"] = qid;
			payload["completed"] = completed;
			payload["pid"] = pid;
			payload["reqItemId"] = reqItemId;
			payload["reqItemQty"] = reqItemQty;
			payload["rewlid"] = rewlid;
			payload["rewItemId"] = rewItemId;
			payload["rewQty"] = rewQty;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to equip an upgrade to a ship
		 * 
		 * @param	shipId
		 * 			The globally unique id for this ship
		 * @param	slotId
		 * 			The id of the slot of the ship to equip the item
		 * @param	upgradeId
		 * 			The id of the item that is to be inserted in the upgrade
		 * @return
		 */
		public static function equipItemToShip(shipId:uint, slotId:uint, itemId:uint, newCapacity:uint, newDefense:uint, 
				newSpeed:uint, newNavigation:uint, newSlots:uint, newConsQty2:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.EQUIP_ITEM_TO_SHIP;
			payload["shipId"] = shipId;
			payload["slotId"] = slotId;
			payload["itemId"] = itemId;
			payload["capacity"] = newCapacity;
			payload["defense"] = newDefense;
			payload["speed"] = newSpeed;
			payload["navigation"] = newNavigation;
			payload["slots"] = newSlots;
			payload["newConsQty2"] = newConsQty2;
			
			sendRequest(payload, callback);
			return true;
		}		
		
		/**
		 * Get information related to crew at a particular location.
		 * 
		 * Should return:
		 * locationId:uint	location of a group of crew
		 * taskId:uint		task that group is doing
		 * quantity:uint	size of the group
		 * 
		 * @return
		 */
		public static function getCrewInfo(pid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_CREW_INFO;
			payload["pid"] = pid;
			
			sendRequest(payload, callback);
			return true;
		}		
		
		/**
		 * Initial request to get all player info
		 * 
		 * 
		 * @param	email
		 * 		The email for this player
		 * 
		 * Will need to get back
		 * pid:uint		The unique id for this player
		 * totalXPEarned:uint
		 * locationId:uint   (where is the ship docked at)
		 * gold:uint
		 * 
		 * @return
		 */
		public static function getPlayerInfo(email:String, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_PLAYER_INFO;
			payload["email"] = email;
			
			sendRequest(payload, callback);
			return true;
		}
		
		
		/**
		 * Request to get all missions and orders that are currently occuring
		 * 
		 * @param	pid
		 * 			The player's unique id
		 * 
		 * Should return:
		 * Data necessary to rebuild the quest
		 * 
		 * @return
		 */
		public static function getQuests(pid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_QUESTS;
			payload["pid"] = pid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to get everything that is for sale inside a particular location
		 * 
		 * @param locationId
		 * 		The location where we want to get the sales.
		 * 
		 * @return
		 */
		public static function getSalesListing(locationId:uint, callback:Function):Boolean
		{
			trace("Sending a get sales list request!!!!!");
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_SALES_LISTING;
			payload["locationId"] = locationId;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to get information about all the ships belonging to a player
		 * 
		 * @param	pid
		 * 
		 * Should return all information about all the player's ship
		 * 
		 * @return
		 */
		public static function getShips(pid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_SHIPS;
			payload["pid"] = pid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to get the list of upgrades that have been attached to a ship
		 * 
		 * @param	shipId
		 * 			The unique id of the ship
		 * 
		 * Should return a list of the
		 * itemId, slotId
		 * for the the requested ship
		 * 
		 * @return
		 */
		public static function getShipEquipment(shipId:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_SHIP_EQUIPMENT;
			payload["shipId"] = shipId;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to get the entire inventory at a particular place.
		 * 
		 * @param pid
		 * 		The unique id of the player
		 * @param lid
		 * 		The location at which the grab the inventory (can be at ship or city)
		 * 
		 * Should give us back the list of items
		 * 
		 * @return
		 */
		public static function getInventory(pid:uint, lid:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.GET_INVENTORY;
			payload["pid"] = pid;
			payload["lid"] = lid;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to transfer a particular item from one location to another.
		 * The new lid gives the destination, should be a city or the ship.
		 * 
		 * @param	invItemId
		 * 			Unique key of this item
		 * @param	lid
		 * 			The location of where to add the goods
		 * @param	quantity
		 * 			The amount to move to that location
		 * @return
		 */
		public static function moveItem(invItemId:uint, lid:uint, quantity:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.MOVE_ITEM;
			payload["invItemId"] = invItemId;
			payload["lid"] = lid;
			payload["quantity"] = quantity;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to update the player's total experience.
		 * 
		 * @return
		 */
		public static function updatePlayerXp(pid:uint, xp:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.UPDATE_PLAYER_XP;
			payload["id"] = pid;
			payload["experience"] = xp;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/**
		 * Request to post, remove, or update a sales listing
		 * 
		 * @param 	invId
		 * 			The unique id for a batch of items
		 * @param	quantity
		 * 			The amount to sell
		 * @param	price
		 * 			The price to sell per unit
		 * 
		 * @return
		 */
		public static function updateSalesListing(invId:uint, quantity:uint, price:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.UPDATE_SALES_LISTING;
			payload["invId"] = invId;
			payload["quantity"] = quantity;
			payload["price"] = price;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function updateQuestContribution(qid:uint, quantity:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.UPDATE_QUEST_CONTRIBUTION;
			payload["id"] = qid;
			payload["newContribution"] = quantity;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function updateTutorialProgress(pid:uint, tutorialStage:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.UPDATE_TUTORIAL_PROGRESS;
			payload["pid"] = pid;
			payload["tutPro"] = tutorialStage;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function updateItemQuantity(invItemId:uint, quantity:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.UPDATE_QTY;
			payload["invItemId"] = invItemId;
			payload["qty"] = quantity;
			
			sendRequest(payload, callback);
			return true;
		}
		
		public static function saleNotifyComplete(invItemId:uint, quantity:uint, callback:Function):Boolean
		{
			var payload:Object = new Object();
			payload["action"] = GCConstants.SALES_NOTIFY_COMPLETED;
			payload["invItemId"] = invItemId;
			payload["soldQtySinceNotify"] = quantity;
			
			sendRequest(payload, callback);
			return true;
		}
		
		/*
		 * Sends payload to server. If expectResponse, then the call was a get and client expects some data back from server
		 * Otherwise, the client only expects some acknowledgement
		 */
		public static function sendRequest(payload:Object, callback:Function):Boolean
		{
			var urlLoader:URLLoader = new URLLoader();
			urlLoader.dataFormat = URLLoaderDataFormat.TEXT;
			var url:String = GCConstants.SERVER_URL + GCConstants.URL_TIME_SUFFIX + 
				new Date().getTime() + GCConstants.URL_DATA_SUFFIX + JSON.encode(payload);
			var request:URLRequest = new URLRequest(url);
			request.method = URLRequestMethod.POST;
			
			urlLoader.addEventListener(Event.COMPLETE, acceptResponse);
			urlLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, httpStatusHandler);
			urlLoader.addEventListener(IOErrorEvent.IO_ERROR, handleError);
			
			urlLoader.load(request);
			
			//trace(url);
			
			// ACK
			function acceptResponse(event:Event):void
			{
				//trace("request success");
				var loader:URLLoader = URLLoader(event.target);
				var dataObj:Object = JSON.decode(loader.data);
				trace(loader.data);
				var ret:uint = dataObj["result"];

				if(callback != null)
					callback(ret, dataObj);
			}
			
			function httpStatusHandler(event:HTTPStatusEvent):void
			{
				//trace("status! " + event);
			}
		
			///Handles I/O errors for server communication. Handles 500 status as well(?)
			function handleError(e:IOErrorEvent):void
			{
				trace("Reached error handler!");
				var loader:URLLoader = URLLoader(e.target);
				trace(loader.data);
				
				// Malformed data, do not try to parse it
				return;
				
				var dataObj:Object = JSON.decode(loader.data);
				var ret:uint = dataObj["result"];
				
				if(callback != null)
					callback(ret, dataObj);
				//trace("Server error (sort of?)! " + e);
				//getURL("javascript:alert('I/O error connecting to server!')");
			}	
			
			return true;
		}
	}

}